/*
 * @(#)BasicModule.java	1.23 02/08/21
 *
 * Copyright (c) 1996-2002 Sun Microsystems, Inc.  All rights reserved.
 */

package com.sun.media;

import javax.media.Buffer;
import javax.media.Format;
import javax.media.Time;


/**
 * BasicModule
 * Implements a basic JMF Module.
 */
abstract public class BasicModule implements Module, StateTransistor {

    /** 
     * registry of all input connectors created by this module. 
     */
    protected Registry inputConnectors = new Registry();

    /** 
     * registry of all output connectors created by this module.
     */
    protected Registry outputConnectors = new Registry();

    protected InputConnector[] inputConnectorsArray;
    protected OutputConnector[] outputConnectorsArray;

    protected int protocol = Connector.ProtocolPush;

    /** 
     * the instance name of the module, declared in the manager 
     */
    protected String name = null;

    protected ModuleListener moduleListener;
    protected BasicController controller;  // the Controller that maintains this module.

    protected boolean resetted = false;
    protected boolean prefetchFailed = false;

    protected JMD jmd = null;
    
    /**
     * This function performs the steps of realizing a module or a Player.
     * @return true if successful.
     */
    public boolean doRealize() { 
	return true;
    }

    /**
     * Called when realize fails.
     */
    public void doFailedRealize() { }

    /**
     * Called when the realize() is aborted, i.e. deallocate() was called
     * while realizing.  Release all resources claimed previously by the
     * realize() call.
     */
    public void abortRealize() { }


    public void connectorPushed(InputConnector inputConnector) {
        process();
    }

    /**
     * This function performs the steps to prefetch a module or Player.
     * @return true if successful.
     */
    public boolean doPrefetch() {
        // commit connectors to array
	resetted = false;
        return true; 
    }

    /**
     * Called when prefetch fails.
     */
    public void doFailedPrefetch() { }

    /**
     * Called when the prefetch() is aborted, i.e. deallocate() was called
     * while prefetching.  Release all resources claimed previously by the
     * prefetch call.
     */
    public void abortPrefetch() { }

    /**
     * This function performs the steps to start a module or Player.
     */
    public void doStart() { 
	resetted = false;
    }

    /**
     * This function performs the steps to stop a module or Player,
     * and return to the prefetched state.
     */
    public void doStop() { }

    /**
     * This function performs the steps to deallocate a module or Player,
     * and return to the realized state.
     */
    public void doDealloc() { }

    /**
     * This function performs the steps to close a module or Player.
     */
    public void doClose() { }

    /**
     * This function notifies the module that the media time has changed.
     */
    public void doSetMediaTime(Time t) { }

    /**
     * This function notifies the module that the playback rate has changed.
     */
    public float doSetRate(float r) { 
	return r; 
    }

    /**
     * Return a list of module controls.
     */
    public Object[] getControls() {
	return null;
    }

    public Object getControl(String s) {
	return null;
    }

    /**
     * Specify a <code>ModuleListener</code> to which this <code>Module</code>
     * will send events.
     *
     * @param listener The listener to which the <code>Module</code> will
     * post events.
     */
    public void setModuleListener(ModuleListener listener) {
	moduleListener = listener;
    }

    /**
     * Selects a format for this Connector (the default is null).
     * The <b>setFormat()</b> method is typically called by the Manager
     * as part of the Connector connection method call.
     * Typically the connector would delegate this call to its owning Module.
     **/
    public void setFormat(Connector connector,Format format) {}

    /**
     * Return an array of strings containing this media module's input
     * port names.
     */
    public String[] getInputConnectorNames() {
        return inputConnectors.getNames();
    }

    /**
     * Return an array of strings containing this media module's output
     * port names.
     */
    public String[] getOutputConnectorNames() {
        return outputConnectors.getNames();

    }

    /**
     * Return the InputConnector given the connector name.
     */
    public InputConnector getInputConnector(String connectorName) {
        return (InputConnector)inputConnectors.get(connectorName);
    }

    /**
     * Return the OutputConnector given the connector name.
     */
    public OutputConnector getOutputConnector(String connectorName) {
        return (OutputConnector)outputConnectors.get(connectorName);

    }

    /**
     * For each of the inputConnectables to this node, it needs to be
     * registered with this function.
     */
    public void registerInputConnector(String name, InputConnector inputConnector) {
       inputConnectors.put(name , inputConnector);
       inputConnector.setModule(this);
    }

    /**
     * For each of the outputConnectables from this node, it needs to be
     * registered with this function.
     */
    public void registerOutputConnector(String name, OutputConnector outputConnector) {
       outputConnectors.put(name , outputConnector);
       outputConnector.setModule(this);

    }

    /**
     * reset this module only.
     * <pre>
     * if (state== Started)
     *    throw Exception()
     * for (all connectors)
     *    connector.reset()
     * </pre>
     * The resetted flag is falsified only when the module is later restarted.
     */
    public void reset() {
	resetted = true;
    }

    /**
     * Verify to see if the given buffer has valid data sizes.
     */
    protected boolean verifyBuffer(Buffer buffer) {
	if (buffer.isDiscard())
	    return true;
	Object data = buffer.getData();
	if (buffer.getLength() < 0) {
	    System.err.println("warning: data length shouldn't be negative: " + buffer.getLength());
	}
	if (data == null) {
	    System.err.println("warning: data buffer is null");
	    if (buffer.getLength() != 0) {
		System.err.println("buffer advertized length = " + buffer.getLength() + " but data buffer is null!"); 
		return false;
	    }
	} else if (data instanceof byte[]) {
	    if (buffer.getLength() > ((byte[])data).length) {
		System.err.println("buffer advertized length = " + buffer.getLength() + " but actual length = " + ((byte[])data).length); 
		return false;
	    }
	} else if (data instanceof int[]) {
	    if (buffer.getLength() > ((int[])data).length) {
		System.err.println("buffer advertized length = " + buffer.getLength() + " but actual length = " + ((int[])data).length); 
		return false;
	    }
	}
	return true;
    }

    /**
     * @return true if the module has been interrupted.
     */
    final public boolean isInterrupted() {
	return (controller == null ? false : controller.isInterrupted());
    }

    /**
     * return if this module create threads (so it run on Safe protocol) 
     * like Rendering module or not (as a codec module).
     **/
    public boolean isThreaded(){
        return true;
    }

    /**
     * return if data is available on all inputConnectors and there 
     * is room in all outputConnectors.
     **/
    public boolean canRun(){
        for (int i=0;i<inputConnectorsArray.length;i++)
         if (!inputConnectorsArray[i].isValidBufferAvailable() )
            return false;
        for (int i=0;i<outputConnectorsArray.length;i++)
         if (!outputConnectorsArray[i].isEmptyBufferAvailable() )
            return false;

        return true;
    }


    /**
     * function which does the real processing.
     * <pre>
     * if canRun {
     *    for (all inputConnectors)
     *      ic.getValidBuffer()
     *    for (all outputConnectors)
     *      oc.getEmptyBuffer()
     *    <process buffer>
     *    for (all inputConnectors)
     *      ic.readReport()
     *    for (all outputConnectors)
     *      oc.writeReport()
     * }
     * </pre>
     **/
    abstract protected void process();

    protected void error(){
        throw new RuntimeException(getClass().getName()+" error");
    }

    /**
     * @return the Controller that maintains this module.
     **/
    final public BasicController getController(){
        return controller;
    }

    /**
     * Set the Controller that maintains this module.
     */
    final public void setController(BasicController c) {
	controller = c;
    }

    /**
     * Return the state of the controller.
     */
    final public int getState() {
	return controller.getState();
    }

    /** 
     * returns the name of this Module in the Player 
     */
    final public String getName(){
        return name;
    }

    /** 
     * sets the name of this Module. Called by the owning Player 
     * registerModule() method 
     */
    public void setName(String name){
        this.name=name;
    }

    public void setJMD(JMD jmd) {
	this.jmd = jmd;
    }
    
    /**
     * Return the current Media time.
     */
    public Time getMediaTime() {
	return controller.getMediaTime();
    }

    /**
     * Return the current time in nanoseconds.
     */
    public long getMediaNanoseconds() {
	return controller.getMediaNanoseconds();
    }

    public long getLatency() {
	return ((PlaybackEngine)controller).getLatency();
    }

    /** 
     * sets the protocol for all the connectors 
     */
    public void setProtocol(int protocol){
        this.protocol=protocol;
        Connector[] connectors= inputConnectors.getConnectors();
        for (int i=0; i<connectors.length; i++)
           connectors[i].setProtocol(protocol);
                    connectors= outputConnectors.getConnectors();
        for (int i=0; i<connectors.length ; i++)
           connectors[i].setProtocol(protocol);

    }

    /** 
     * return the data transfer protocol 
     */
     public int getProtocol(){
        return protocol;
    }

    public boolean prefetchFailed() {
	return prefetchFailed;
    }


 /////////////////////////////////////
 // inner classes 
 /////////////////////////////////////

 /**
  * Connectors Registry.
  **/
 class Registry extends java.util.Hashtable {

    Connector def = null;	// the Default connector.

    /** returns the names of all Connectors in this Registry **/
    String[] getNames (){
        java.util.Enumeration namesEnum=keys();
        String[] namesArray=new String[size()];
        for (int i=0;i<size();i++)
          namesArray[i]=(String)namesEnum.nextElement();
        return namesArray;
    }

    /** register Connector.
     * @exception RuntimeException If the specified name is already registered
     **/
    void put(String name, Connector connector) {
        if (containsKey(name))
           throw new RuntimeException("Connector '"+name+"' already exists in Module '"+BasicModule.this.getClass().getName()+"::"+name+"'");
	if (def == null)
	    def = connector;
         super.put(name,connector);
    }

    /**
     * Return the default if null (wildcard) is passed in. 
     */
    Object get(String name) {
	if (name == null)
	    return def;
	return super.get(name);
    }

    /** returns all Connectors in this Registry **/
    Connector[] getConnectors (){
        java.util.Enumeration connectorsEnum=elements();
        Connector[] connectorsArray=new Connector[size()];
        for (int i=0;i<size();i++)
          connectorsArray[i]=(Connector)connectorsEnum.nextElement();
        return connectorsArray;
    }

 }

}

